package jwtauth

import (
	"fmt"
	"net/http"
	"time"

	"gitee.com/linxing_3/youye-core/sdk/pkg/jwtauth/user"
	"gitee.com/linxing_3/youye-core/sdk/pkg/utils"
	"gitee.com/linxing_3/youye-core/storage"

	"github.com/gin-gonic/gin"
	"github.com/spf13/cast"
)

type GinSessionOption func(*GinSessionAuth)

// 缓存空间
func SessionWithStorage(arg storage.AdapterCache) GinSessionOption {
	return func(a *GinSessionAuth) { a.Storage = arg }
}

// 缓存key前缀
func SessionWithPrefixKey(arg string) GinSessionOption {
	return func(a *GinSessionAuth) { a.PrefixKey = arg }
}

// 有效期(秒)
func SessionWithExpired(arg int) GinSessionOption {
	return func(a *GinSessionAuth) { a.Expired = arg }
}

// gin上下文中的session key
func SessionWithGinCtxSessionKey(arg string) GinSessionOption {
	return func(gsa *GinSessionAuth) { gsa.GinCtxSessionKey = arg }
}

// 获取token的key
func SessionWithTokenName(arg string) GinSessionOption {
	return func(a *GinSessionAuth) { a.TokenName = arg }
}

// 可以获取到token的位置 引文,分割   header,query,cookie
func SessionWithTokenLookup(arg []string) GinSessionOption {
	return func(a *GinSessionAuth) { a.TokenLookup = arg }
}

// 会话信息构造函数
func SessionWithSessionInfoConstructor(arg func(map[string]string) (user.ISessionInfo, error)) GinSessionOption {
	return func(a *GinSessionAuth) { a.SessionInfoConstructor = arg }
}
func SessionWithCustomLoginHandler(arg IAuthLoginHandler) GinSessionOption {
	return func(a *GinSessionAuth) { a.CustomLoginHandler = arg }
}

func NewGinSessionAuth(opts ...GinSessionOption) (IAuth, error) {
	defaultSess := &GinSessionAuth{
		PrefixKey:              "session",
		Expired:                3600 * 12,
		GinCtxSessionKey:       "session",
		TokenName:              "sessionid",
		TokenLookup:            []string{"header"},
		SessionInfoConstructor: func(map[string]string) (user.ISessionInfo, error) { return &user.SessionInfo{}, nil },
	}

	for _, opt := range opts {
		opt(defaultSess)
	}
	if defaultSess.CustomLoginHandler == nil {
		return nil, fmt.Errorf("CustomLoginHandler is nil")
	}
	return defaultSess, nil
}

type GinSessionAuth struct {
	Storage   storage.AdapterCache // 缓存空间
	PrefixKey string               // 缓存key前缀
	Expired   int                  // 有效期(秒)

	GinCtxSessionKey string // gin上下文中的session key

	TokenName   string   // 获取token的key
	TokenLookup []string // 可以获取到token的位置 引文,分割   header,query,cookie

	SessionInfoConstructor func(map[string]string) (user.ISessionInfo, error) // 会话信息构造函数

	CustomLoginHandler IAuthLoginHandler
}

func (g *GinSessionAuth) LoginHandler(ctx *gin.Context) {
	// 生成token

	sessionInfo, err := g.CustomLoginHandler(ctx)
	if err != nil {
		ctx.AbortWithStatusJSON(http.StatusUnauthorized, &LoginResponse{
			Code: http.StatusUnauthorized,
			Msg:  err.Error(),
		})
		return
	}

	endtime := time.Now().Add(time.Second * time.Duration(g.Expired))

	token, err := g.UpdateSession(sessionInfo, endtime)
	if err != nil {
		ctx.AbortWithStatusJSON(http.StatusInternalServerError, &LoginResponse{
			Code: http.StatusInternalServerError,
			Msg:  err.Error(),
		})
		return
	}
	/*
		claim := sessionInfo.GetClaim()
		claim["expire"] = endtime.Format(time.DateTime)
		claim["timeStamp"] = cast.ToString(time.Now().Unix())

		tokenid := utils.HmacObj(claim)
		if tokenid == "" {
			ctx.AbortWithStatusJSON(http.StatusInternalServerError, &LoginResponse{
				Code: http.StatusInternalServerError,
				Msg:  "token生成失败",
			})
			return
		} else {
			claim["sessionId"] = tokenid
		}

		sessionkey := g.PrefixKey + "_" + tokenid
		if err := g.Storage.HashSet(sessionkey, claim, g.Expired); err != nil {
			ctx.AbortWithStatusJSON(http.StatusInternalServerError, &LoginResponse{
				Code: http.StatusInternalServerError,
				Msg:  err.Error(),
			})
			return
		}
	*/
	thirdInfo := make(map[string]interface{})
	for k, v := range sessionInfo.GetThird() {
		thirdInfo[k] = v
	}
	ctx.AbortWithStatusJSON(http.StatusOK, &LoginResponse{
		Code:   http.StatusOK,
		Token:  token,
		Expire: int(endtime.Unix()),
		Msg:    "ok",
		Third:  thirdInfo,
	})
}

func (g *GinSessionAuth) UpdateSession(info user.ISessionInfo, expiredAt time.Time) (string, error) {
	claim := info.GetClaim()
	claim["expire"] = expiredAt.Format(time.DateTime)
	claim["timeStamp"] = cast.ToString(time.Now().Unix())

	tokenid := utils.HmacObj(claim)
	if tokenid == "" {
		return "", fmt.Errorf("token生成失败")
	} else {
		claim["sessionId"] = tokenid
	}

	sessionkey := g.PrefixKey + "_" + tokenid
	if err := g.Storage.HashSet(sessionkey, claim, g.Expired); err != nil {
		return tokenid, fmt.Errorf("session更新失败: %w", err)
	}

	return tokenid, nil
}

func (g *GinSessionAuth) DeleteSession(ctx *gin.Context) error {
	token, err := g.getToken(ctx)
	if err != nil {
		return err
	}
	sessionkey := g.PrefixKey + "_" + token
	return g.Storage.Del(sessionkey)
}

// 获取token
func (g *GinSessionAuth) getToken(ctx *gin.Context) (string, error) {

	for _, f := range g.TokenLookup {
		switch f {
		case "header":
			token := ctx.Request.Header.Get(g.TokenName)
			if token != "" {
				return token, nil
			}
		case "query":
			token := ctx.Query(g.TokenName)
			if token != "" {
				return token, nil
			}
		case "cookie":
			cookie, err := ctx.Request.Cookie(g.TokenName)
			if err != nil {
				continue
			} else if cookie.Value != "" {
				return cookie.Value, nil
			}
		}
	}
	return "", fmt.Errorf("token not found from lookup [%s] with key [%s]", g.TokenLookup, g.TokenName)
}

func (g *GinSessionAuth) GetSessionInfo(ctx *gin.Context) (user.ISessionInfo, error) {
	sessinf, exist := ctx.Get(g.GinCtxSessionKey)
	if exist {
		usess, ok := sessinf.(user.ISessionInfo)
		if ok {
			return usess, nil
		}
	}

	token, err := g.getToken(ctx)
	if err != nil {
		return nil, err
	}
	sessionkey := g.PrefixKey + "_" + token
	sessionInfo, err := g.Storage.HashGetAll(sessionkey)
	if err != nil {
		return nil, err
	} else if len(sessionInfo) == 0 {
		return nil, fmt.Errorf("session not found")
	}
	sessObj, err := g.SessionInfoConstructor(sessionInfo)
	if err != nil {
		return nil, err
	}
	return sessObj, nil
}

func (g *GinSessionAuth) MiddlewareFunc() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		sessinf, err := g.GetSessionInfo(ctx)

		if err != nil {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, &LoginResponse{
				Code: http.StatusUnauthorized,
				Msg:  err.Error(),
			})
			return
		}

		ctx.Set(g.GinCtxSessionKey, sessinf)
		ctx.Next()
	}
}
